'''
Created on Nov 21, 2009

@author: Team Kniggit
Daniel Demski
Alan Rash
Alison Rooks
Matthew Slominski

This is a python Midi player. Currently, the program works by selecting a file from the open
file dialog in the menu bar, which will put that file into the playlist, and then when you select
a file from a playlist and click play, the file will play. The volume slider to the right can be
used to control the volume, but the top slider bar controlling the time in the song currently
does not function. Also as of now the forward and back buttons do not fucntion, and the pause
button simply stops the music.

For this program to run also requires wxpython and pygame installed on the machine. wxpython can
be found here at http://www.wxpython.org/download.php, and pygames can be found at http://www.pygame.org/download.shtml.
'''
#!/usr/bin/python

# yamp.py

import wx
import pygame
import os
import threading
import time
import math

class Player(wx.Frame):
    def __init__(self, parent, id, title):
        """
        The constructer will be responsible for building the GUI
        """

        #Construct the frame of the GUI
        wx.Frame.__init__(self, parent, id, title, size=(350, 300))
        panel = wx.Panel(self, -1)

        #Both a play-all option and frequency modulation function are in the works
        self.playAll = False
        self.lowFreq = False
        
        #Cycle volume option
        self.cycleVolume = False

        pnl2 = wx.Panel(self, -1 )
        #construct the menu-bar and append file help and options menus
        menubar = wx.MenuBar()
        file = wx.Menu()
        help = wx.Menu()
        options = wx.Menu()
       
        #Append options within the menu-bar menus
        file.Append(101, '&Open', 'Open a MIDI file')
        file.Append(102, '&Exit', 'Quit application')
        help.Append(201, '&About', 'Credits')

        #These are also possible addition for a play-all and frequency modulation
        #options.Append(303, 'Low Freq',kind = wx.ITEM_CHECK)
        #options.Append(301, 'Play &All', kind = wx.ITEM_CHECK)
        
        #Append the cycle volume option to the menu-bar
        options.Append(302, 'Cycle Volume', kind = wx.ITEM_CHECK)
        

        #Add menus to the menubar
        menubar.Append(file, '&File')
        menubar.Append(help, '&Help')
        menubar.Append(options, '&Options')
       
        #Initial play-list song 
        examples = ["PODJST.mid"]
       
        #Create the listbox box
        listbox1 = wx.ListBox(self, -1, pos= (50,170), size = (342, 135), choices = examples, name="MIDIS")

        #Add listbox and menu to the GUI
        self.playlist = listbox1
        self.SetMenuBar(menubar)

        #Set up bind statements for events
        self.Bind(wx.EVT_MENU, self.OnOpen, id = 101)
        self.Bind(wx.EVT_MENU, self.TimeToQuit, id = 102)
        self.Bind(wx.EVT_MENU, self.OnAbout, id = 201)
        self.Bind(wx.EVT_MENU, self.OnCycleVolume, id = 302)
        
        #Features to be added
        self.Bind(wx.EVT_MENU, self.OnPlayAll, id = 301)
        self.Bind(wx.EVT_MENU, self.OnLowFreq, id = 303)
       
        #Following will add slider bars and buttons to the GUI
        self.slider1 = wx.Slider(pnl2, -1, 0, 0, 1000)
        self.slider1.SetMin(0)

        #Adds all of the buttons to the GUI
        play_bmp = wx.Bitmap('play.bmp')
        play_bmp.SetMask(wx.Mask(play_bmp, wx.Colour(255,0,255)))
        pause_bmp = wx.Bitmap('pause.bmp')
        pause_bmp.SetMask(wx.Mask(pause_bmp, wx.Colour(255,0,255)))
        start_bmp = wx.Bitmap('start.bmp')
        start_bmp.SetMask(wx.Mask(start_bmp, wx.Colour(255,0,255)))
        end_bmp = wx.Bitmap('end.bmp')
        end_bmp.SetMask(wx.Mask(end_bmp, wx.Colour(255,0,255)))

        pause = wx.BitmapButton(pnl2, -1, pause_bmp)
        play  = wx.BitmapButton(pnl2, -1, play_bmp)
        next  = wx.BitmapButton(pnl2, -1, start_bmp)
        prev  = wx.BitmapButton(pnl2, -1, end_bmp)
        self.slider2 = wx.Slider(pnl2, -1, 0, 0, 100, size=(120, -1))
        self.slider2.SetMin(0)
        self.slider2.SetMax(100)
        self.slider2.SetValue(80)
       
        #Example MIDI
        self.music = "PODJST.mid"

        #Arguments to be passed to pygame.mixer
        self.freq = 44100    # audio CD quality
        self.bitsize = -16   # unsigned 16 bit
        self.channels = 2    # 1 is mono, 2 is stereo
        self.buffer = 1024    # number of samples
        pygame.mixer.init(self.freq, self.bitsize, self.channels, self.buffer)
        #Set initial volume
        pygame.mixer.music.set_volume(0.8)

        #Bind statements for the GUI buttons
        self.Bind(wx.EVT_BUTTON, self.play_music, source=play)
        self.Bind(wx.EVT_BUTTON, self.stop, source=pause)
        self.Bind(wx.EVT_SLIDER, self.adjust_vol, source = self.slider2)

        #These boxes are used to set up the layout
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox2 = wx.BoxSizer(wx.VERTICAL)
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        vbox3 = wx.BoxSizer(wx.VERTICAL)
        hbox3 = wx.BoxSizer(wx.HORIZONTAL)
        hbox3.Add(vbox3)

        #Adding all the components to the different layout boxes
        hbox1.Add(self.slider1, 1)
        hbox2.Add(pause)
        hbox2.Add(play, flag=wx.RIGHT, border=5)
        hbox2.Add(next, flag=wx.LEFT, border=5)
        hbox2.Add(prev)
        hbox2.Add((-1, -1), 1)
        hbox2.Add(self.slider2, flag=wx.TOP | wx.LEFT, border=5)
        hbox3.Add(listbox1, flag=wx.EXPAND)
        vbox.Add(hbox1, flag=wx.EXPAND | wx.BOTTOM, border=10)
        vbox.Add(hbox2, 1, wx.EXPAND)
        vbox2.Add(hbox3, 1, wx.EXPAND | wx.BOTTOM, border=5)
        
        pnl2.SetSizer(vbox)

        #Setting how the GUI will expand/collapse
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(hbox3, 1, flag=wx.EXPAND|wx.TOP)
        sizer.Add(pnl2, flag=wx.EXPAND | wx.BOTTOM | wx.TOP, border=10)

        self.SetMinSize((350, 300))
        self.CreateStatusBar()
        self.SetSizer(sizer)
       
        #Show the GUI
        self.Centre()
        self.Show()

    def play_music(self, event):
        """
        stream music with mixer.music module in blocking manner
        which will stream the sound from disk while playing
        """
       
        clock = pygame.time.Clock()
        #Currently working on a playall feature, not functioning yet, 
        #so the if is skipped by default and the else block is performed
        if self.playAll:
            selection = str(self.playlist.GetStringSelection())
            try:
                pygame.mixer.music.load(selection)
                for song in selection:
                    pygame.mixer.music.queue(self.playlist.GetItems().remove(selection))
            except pygame.error:
                print "Play All did not work. Please deselect it in the Options menu."
            pygame.mixer.music.play()
        #This is where the call to pygame.mixer is made to play the selection from the playlist
        else:
            selection = str(self.playlist.GetStringSelection())
            if (not isinstance(selection, str)) or len(selection) == 0:
                selection = 'rollin.mid'
            try:
                pygame.mixer.music.load(selection)
                pygame.mixer.music.play()
                #print "Music file %s loaded!" % self.music
            except pygame.error:
                print "File %s not found! (%s)" % (selection, pygame.get_error())
                return
            playtimer = PlayUpdater()
            playtimer.start()
   
    def OnAbout(self, event):
        """
        Event handler for the about menu item
        """
        dlg = wx.MessageDialog(self, "This sample program shows off\n"
                              "frames, menus, statusbars, and this\n"
                              "message dialog.",
                              "About Me", wxOK | wxICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()


    def OnOpen(self, event):
        """
        event handler for the open menu option, to get a file from the system
        """
        #Open a file dialog to select the MIDI
        dlg = wx.FileDialog(self, message="Choose a MIDI", wildcard="All Files|*.*|MID files|*.mid|MIDDI files|*.midi")
        if dlg.ShowModal() == wx.ID_OK:
            self.playlist.Insert(dlg.GetPath(), 0)
            self.playlist.Select(0)
        else:
            print 'Nothing was Selected'
        dlg.Destroy()

    def OnPlayAll(self, event):
        """
        Developing a playall feature, which is currently not working correctly and
        being developed
        """
        self.playAll = event.Checked

    def OnCycleVolume(self, event):
        """ OnCycleVolume(event)
        Updates the flag for the cycle volume filter
        """
        self.cycleVolume = event.Checked

    def OnLowFreq(self, event):
        """
        This is currently is development
        Updates the flag for the low frequency filter
        """
        self.lowFreq = event.Checked
        pygame.mixer.init(self.freq, self.bitsize, self.channels, self.buffer)

    def stop(self, event):
        """
        Stop menu option handler
        """
        pygame.mixer.music.stop()

    def adjust_vol(self, event):
        """
        event handler for the adjusting of the volume with the slider bar
        """
        pygame.mixer.music.set_volume(self.slider2.GetValue()/100.0)

    def update_time(self):
        """
        Updates the slider for play time.
        """
        self.slider1.SetMax(1000)
        self.slider1.SetValue(pygame.mixer.music.get_pos()/1000)
        if self.cycleVolume:
            pygame.mixer.music.set_volume(math.sin(pygame.mixer.music.get_pos()*0.05))

    def TimeToQuit(self, event):
        """
        event handler for the quit menu option
        """
        self.Close(True)


class PlayUpdater(threading.Thread):
    def run(self):
        """
        This is the run method using threading techniques
        """
        while (pygame.mixer.music.get_busy()):
            yamp.update_time()
            time.sleep(0.1)

app = wx.App()
yamp = Player(None, -1, 'Player')
app.MainLoop()